/* 
 * File:   buffer_cache.c
 * Author: Tomás R. González Barroso  
 * Diseño de sistemas Operativos
 * Created on 26 de junio de 2013, 8:46
 */

#include "buffer_cache.h"
#include "comun.h"
#include "buffer.h"
#include "semaforo.h"
#include "tf.h"

#include <sys/shm.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
 

//Buffer Cache
buffcach * Buffer_Cache;

// Semaforo para exclusión mutua buffers
int semid;
// Semaforo para exclusión mutua ficheros
int semfid;


int bc_init_memory (int getmemory) {
    key_t shkey;                //Identificador para memoria compartida 
    int id_memoria = -1;  
    
    shkey = ftok ("/bin/ls", SMEMKEY);
    if (shkey==-1) {
        perror("Error generando clave para memoria compartida");
        exit(1);
    }
    
   //Creamos memoria compartida o la obtenemos si ya está creada.
    if (getmemory) 
       id_memoria = shmget (shkey, sizeof(buffcach) , 0777 | IPC_CREAT);
    else
       id_memoria = shmget (shkey, sizeof(buffcach) , 0777);
    
    if (id_memoria == -1)
    {
        perror("Error obteniendo ID memoria compartida");
        exit(1);
    }


    Buffer_Cache = (buffcach *)shmat (id_memoria, (char *)0, 0);
    if (Buffer_Cache == NULL)
    {
        perror("Error obteniendo memoria compartida");
        exit(1);
    }
    
    if (getmemory) {
       printf("Buffer cache alojado correctamente en memoria compartida. "
            "ID:%i\n",id_memoria);           
      }
       
    else
       printf("Buffer apuntado correctamente. "
            "ID:%i\n",id_memoria);    
    
    return id_memoria;
}

void bc_free_mem (int id_memoria,int getmemory) {

    shmdt ((char *)Buffer_Cache);
    if (getmemory)
       shmctl (id_memoria, IPC_RMID, (struct shmid_ds *)NULL);
    printf("Buffer cache liberado.\n");

}

void bc_init_data(void) {
    int i,j;
    
    // initialize free list
    Buffer_Cache->header.fl_start = 0;
    Buffer_Cache->header.fl_end = MAXBUFF - 1;

    // initialize hash list
    for (i = 0; i < MAXHASH; i++) 
        Buffer_Cache->header.hash[i] = -1;

    // initialize slept list
    for (i = 0; i < MAXSLEEP; i++)
        Buffer_Cache->header.sleep[i] = -1;

    // initialize buffers and link free list nodes
    for (i = 0; i < MAXBUFF; i++) {
        Buffer_Cache->buffers[i].id = i;
        Buffer_Cache->buffers[i].fd = -1;
        Buffer_Cache->buffers[i].block = -1;
        Buffer_Cache->buffers[i].tam = -1;
        Buffer_Cache->buffers[i].pid = -1;
        Buffer_Cache->buffers[i].data[0] = '\0';
        Buffer_Cache->buffers[i].status_delayed = FALSE;            
        Buffer_Cache->buffers[i].status_locked = FALSE;            
        Buffer_Cache->buffers[i].status_valid = FALSE;            
        if (i != 0)
            Buffer_Cache->buffers[i].freeprev = i - 1;
        else
            Buffer_Cache->buffers[i].freeprev = MAXBUFF - 1;
        if (i < MAXBUFF-1)
            Buffer_Cache->buffers[i].freenext = i + 1;
        else
            Buffer_Cache->buffers[i].freenext = 0;
        
        Buffer_Cache->buffers[i].hashnext = -1;
        Buffer_Cache->buffers[i].hashprev = -1;
        for (j = 0; j < MAXSLEEP; j++)
            Buffer_Cache->buffers[i].sleep[j] = -1;
    }    
}

//Visualizamos el buffer cache
void bc_print (void) {
    int i,j,tmp;
    buffer *tmp_buffer = NULL;
    
    printf("BC_FREE_START => %d \n" ,Buffer_Cache->header.fl_start);
    printf("BC_FREE_END => %d \n" ,Buffer_Cache->header.fl_end);    
    //Visualizamos Hash
    for (i=0;i < MAXHASH;i++) {
        printf("H%i => ",i);
        tmp = Buffer_Cache->header.hash[i];                
        while (tmp != -1) {
            tmp_buffer = &Buffer_Cache->buffers[tmp];
            printf("B%i,",tmp_buffer->id);            
            tmp = tmp_buffer->hashnext;              
        }
        printf("\n");            
    }
    //Visualizamos Lista free
    tmp = Buffer_Cache->header.fl_start;
    printf("Lista Libres => ");
    while (tmp != -1) {   
        tmp_buffer = &Buffer_Cache->buffers[tmp];
        printf("B%i,",tmp_buffer->id);                
        if (tmp_buffer->id == Buffer_Cache->header.fl_end) 
            tmp = -1;
        else 
            tmp = tmp_buffer->freenext;           
    }
    printf("\n");                
    //Visualizamos Lista procesos en espera (Sleep)    
    printf("Lista Procesos esperando Buffers Libres => ");
    for (i=0;i < MAXSLEEP;i++) {    
        tmp = Buffer_Cache->header.sleep[i];
        if (tmp != -1)
           printf("PID(%i),",tmp);        
    }
    printf("\n");                
 }


//Generamos n caracteres aleatorios y los guardamos en la cadena
//Suponemos cadena con el tamaño adecuado
void random_char(char * datos,int n) {
    int i;
        
    for (i=0; i<n; i++)         
        datos[i] = (char) (rand() % 90 + 33);
       
}

//Inicializamos buffer, creamos memoria compartida, inicializamos punteros
//Inicializamos estructura ficheros en memoria compartida
//mantenemos en memoria hasta pulsación 'f'
void bc_init(int getmemory,int ite) {
    
    int id_memoria, id_memoria1;
    
    if (ite !=2 ) {
        id_memoria = bc_init_memory(getmemory);
        if (getmemory) {  
             //Inicializamos Semaforo exclusión mutua buffers
            semid = semInit( SEMID, (short)1);
            //Inicializamos Semaforo exclusión mutua sitema ficheros
            semfid = semInit( SEMFID, (short)1);
            printf("Creado Semaforo_Buff(%i), Creado Semaforo_Fich(%i)\n",semid,semfid);
            bc_init_data();        
        }
        else {
            semid = semAt( SEMID);                        
            semfid = semAt( SEMFID);
            printf("Semaforo_Buff(%i),Semaforo_Fich(%i)\n",semid,semfid);
        }

        id_memoria1 = ft_init_memory(getmemory);
        if (getmemory) {                
            ft_init_data();        
        }

        //Inicializamos TDFU
        s_init();            
    }
    
    int salir = -1;
    int fx;
    if (ite != 0)
        salir = 0;
    //Esperamos pulsación de tecla F para finalizar y liberar memoria.
    do {
       if (ite == 0) fx = getch();        
       if ((fx == (int) 'q') || (fx == (int)'Q'))
           salir = 0;
       //Visualizamos Buffer
       if ((fx == (int) 'm') || (fx == (int)'M'))
           bc_print();
       //Visualizamos Buffer
       if ((fx == (int) 'b') || (fx == (int)'B')) {
          int n=0;
          printf("Nº Buffer: ");
          scanf("%d",&n);
          bprint(&Buffer_Cache->buffers[n]);
       }
       //LLamada a la funcion getblk
       if ((fx == (int) 'g') || (fx == (int)'G')) {
           buffer * tmpbuffer = NULL;           
           int fs = 0;
           int blk;
           ino_t inode=0;
           printf("Parametros getblk: \n");
           printf("inode: ");
           scanf("%d",&blk);      
           inode = (ino_t)blk;
           printf("Bloque: ");
           scanf("%d",&blk);
           printf("getblk(%d %d %d)\n",fs,(int)inode,blk);
           tmpbuffer = getblk(fs,inode,blk);
           printf("Buffer => B(%d)", tmpbuffer ->id);
       }
        //LLamada a la funcion brelse
       if ((fx == (int) 'f') || (fx == (int)'F')) {
           buffer * tmpbuffer = NULL;  
           int n=0;
           printf("Nº Buffer: ");
           scanf("%d",&n);      
           tmpbuffer = &Buffer_Cache->buffers[n];
           printf("brelse(%d)\n",tmpbuffer->id);
           brelse(tmpbuffer);
       }
         //LLamada a la funcion open
       if ((fx == (int) 'o') || (fx == (int)'O')) {           
           char path[255];
           printf("RUTA: ");
           scanf("%s",path);                 
           printf("open('%s',O_RDWR)\n",path);
           s_open(path,O_RDWR);           
       }
       //LLamada a la funcion close
       if ((fx == (int) 'c') || (fx == (int)'C')) {           
           int fd = 0;
           printf("fd:");
           scanf("%d",&fd);
           printf("close('%d')\n",fd);
           s_close(fd);
       }
       // read       
       if ((fx == (int) 'r') || (fx == (int)'R')) {
           int tam = 1000;                  
           int fd = 0;
           printf("fd:");
           scanf("%d",&fd);
           printf("Nº bytes a leer: ");
           scanf("%d",&tam);
           char data[tam];                      
           printf("read(%d,data,%d)\n",fd,tam);           
           tam = s_read(fd,data,tam);
           printf("data[%d]\n",tam);           
       }
       
       if ((fx == (int) 'w') || (fx == (int)'W')) {           
           int tam = BLKSIZE;                  
           int fd = 0;
           printf("fd:");
           scanf("%d",&fd);
           printf("Nº bytes a escribir: ");
           scanf("%d",&tam);
           char data[tam];
           //Cargamos con datos aleatorios
           random_char(data,tam);
           printf("write(%d,data,%d)\n",fd,tam); 
           //Nos posicionamos al final del fichero
           //s_lseek(fd,0,SEEK_END);          
           //Escribimos
           s_write(fd,data,tam);
           
       }
       
       if ((fx == (int) 'l') || (fx == (int)'L')) {
           int pos = 0;                  
           int fd = 0;
           int a;
           
           printf("fd:");
           scanf("%d",&fd);
           printf("Desplazamiento: ");
           scanf("%d",&pos);
           printf("¿Absoluto?: ");           
           a = getch();        
           if ((a == (int) 's') || (a == (int)'S')) {
              printf("lseek(%d,%d,SEEK_SET)\n",fd,pos);           
              s_lseek(fd,pos,SEEK_SET);
           }
           else {
              printf("lseek(%d,%d,SEEK_CUR)\n",fd,pos);           
              s_lseek(fd,pos,SEEK_CUR);
           }
           
       }
       //Visualizamos Tabla de Ficheros
       if ((fx == (int) 't') || (fx == (int)'T'))
           tf_print();
       //Visualizamos TDFU
       if ((fx == (int) 'u') || (fx == (int)'U'))
           s_print();
    }
    while (salir != 0);   
    
    if (ite != 1) {
      bc_free_mem(id_memoria,getmemory);
      tf_free_mem(id_memoria,getmemory); 
      if (getmemory) {
         //liberamos semaforos
         semFree( semid);         
         semFree(semfid);
         printf("Semaforos Liberados.\n",semid,semfid);
      }
    }
}

//Añadimos el buffer indicado a la lista libres (al final)
void bc_setfree(buffer *buff) {
    
    buffer * tmp_buffer = NULL;
    
    buff->freenext = -1;
    buff->freeprev = -1;
    if (Buffer_Cache->header.fl_end == -1) {  //Sin elementos
        Buffer_Cache->header.fl_end =   buff->id;
        Buffer_Cache->header.fl_start = buff->id;
    }
    else {
        buff->freenext = Buffer_Cache->header.fl_start;
        tmp_buffer = &(Buffer_Cache->buffers[Buffer_Cache->header.fl_end]);
        tmp_buffer->freenext = buff->id;        
        buff->freeprev = tmp_buffer->id;
        Buffer_Cache->header.fl_end = buff->id;
        tmp_buffer = &(Buffer_Cache->buffers[Buffer_Cache->header.fl_start]);
        tmp_buffer ->freeprev = buff->id;
        
    }
}

// Obtenemos un buffer de la lista libre
// por defecto el primero de la lista
// si le indicamos uno, libera ese
buffer * bc_getfree(buffer *buffsol) {    
    buffer *buff = NULL;
    buffer *buffaux = NULL;
    

    if (buffsol != NULL )
       if (buffsol -> id == Buffer_Cache->header.fl_start) 
           buffsol = NULL;

    if (Buffer_Cache->header.fl_start != -1) {           
        if (Buffer_Cache->header.fl_start != Buffer_Cache->header.fl_end) {
            //Lo sacamos de la lista de libres
            if (buffsol == NULL )  {
                buff = &(Buffer_Cache->buffers[Buffer_Cache->header.fl_start]);
                buffaux = &(Buffer_Cache->buffers[buff->freenext]);
                buffaux->freeprev = Buffer_Cache->header.fl_end;
                Buffer_Cache->header.fl_start = buffaux->id;   
                buffaux = &(Buffer_Cache->buffers[Buffer_Cache->header.fl_end]);
                buffaux->freenext = Buffer_Cache->header.fl_start;
            }
            else {
                buff = buffsol;
                buffaux = &(Buffer_Cache->buffers[buff->freenext]);
                buffaux->freeprev = buff ->freeprev;                 
                buffaux = &(Buffer_Cache->buffers[buff->freeprev]);
                buffaux->freenext = buff ->freenext;
                if (buff->id == Buffer_Cache->header.fl_end) 
                    Buffer_Cache->header.fl_end = buff->freeprev;   
            }
        }
        else {  //Es el unico buffer existente  
            buff = &(Buffer_Cache->buffers[Buffer_Cache->header.fl_start]);
            Buffer_Cache->header.fl_start = -1;
            Buffer_Cache->header.fl_end = -1;
            printf("Extrayendo ultimo buffer libre.\n");
        }
        buff->freenext = -1;
        buff->freeprev = -1;
    }    
    return buff;
}

//Eliminamos de la lista hash el buffer indicado
void bc_drophash( buffer * buff) {
    
    int pos = -1, ant = -1,sig = -1;
    int encontrado = FALSE;    
    int salir = FALSE;
    buffer * buff_aux = NULL;
    int blk = -1, hash = -1;
    ino_t inode;
    
    blk = buff->block;
    if (blk != -1) {  //Nos aseguramos que este inicializado.Blk Existe
        inode = buff->inode;        
        hash = blk % MAXHASH;
        pos =  Buffer_Cache->header.hash[hash];
    }
    if (pos != -1) {        
      do {
          //Buscamos buffer 
          buff_aux = &(Buffer_Cache ->buffers[pos]);
          if ((buff_aux->inode == inode) && (buff_aux->block == blk)) {
              encontrado = TRUE;
              salir = TRUE;
          }
          else 
             pos = buff_aux->hashnext;          
          if (pos == -1) 
              salir = TRUE;
      }
      while (!salir);            
    }
    if (encontrado) {  
      printf("Eliminando de Hash:%d\n",hash);
      ant = buff_aux -> hashprev;
      sig = buff_aux -> hashnext;
      if (ant == -1)  //Es el primero
         Buffer_Cache ->header.hash[hash] = buff_aux ->hashnext;
      else {
         buff_aux = &(Buffer_Cache->buffers[ant]);
         buff_aux->hashnext = sig;
      }
      if (sig != -1) {               
        buff_aux = &(Buffer_Cache->buffers[sig]);
        buff_aux->hashprev = ant;
      }
      
    }   
}

//Añadimos al comienzo de la hash el buffer indicado
void bc_pushhash( buffer * buff_aux) {
     
    int hash = -1;
    buffer * buff = NULL;
    
    hash = buff_aux->block % MAXHASH;  
    printf("Insertando en hash:%d\n",hash);    
    buff_aux->hashprev = -1;
    buff_aux->hashnext = Buffer_Cache->header.hash[hash];
    Buffer_Cache->header.hash[hash] = buff_aux->id;
    if (buff_aux->hashnext != -1) {
      buff = &(Buffer_Cache->buffers[buff_aux->hashnext]);  
      buff -> hashprev = buff_aux->id;      
    }
}